package org.flexthinker.binding.value.support
{
    import org.flexthinker.binding.value.PropertyChangeListener;
    import org.flexthinker.binding.value.ValueChangeDetector;
    import org.flexthinker.binding.value.ValueModel;

    import mx.events.PropertyChangeEvent;

    /**
     * <p>AbstractValueModel minimizes the effort required to implement this interface.
     * It uses the PropertyChangeSupport to fire PropertyChangeEvents, and it adds
     * PropertyChangeListeners for the specific property name "value". This ensures
     * that the constraint mentioned above is met.
     */
    public class AbstractValueModel extends PropertyChangePublisherImpl implements ValueModel
    {
        private var _valueChangeDetector:ValueChangeDetector;

        protected var changeListeners:Vector.<PropertyChangeListener> = new Vector.<PropertyChangeListener>();

        /**
         *
         * @param host the object which contains the <code>popertyName</code>
         * @param propertyName
         */
        public function AbstractValueModel( host:Object, propertyName:String )
        {
            super( host, propertyName );
            init();
        }

        private function init():void
        {
            if( ! host || ! propertyName )
            {
                return;
            }
            // hook up a listener to observer changes in the propertyName
            addPropertyChangeListener( new DelegatingPropertyChangeListener( propertyChangedHandler ) );
        }

        public function getValue():Object
        {
            throw new Error( "you have to implement getValue() method when extending AbstractValueModel" )
        }

        public function setValue( value:Object ):void
        {
            throw new Error( "you have to implement setValue() method when extending AbstractValueModel" )
        }

        public function addValueChangeListener( listener:PropertyChangeListener ):void
        {
            changeListeners.push( listener );
        }


        public function removeValueChangeListener( listener:PropertyChangeListener ):void
        {
            var idx:int = changeListeners.indexOf( listener );
            if ( idx >= 0 )
            {
                changeListeners.splice(idx, 1);
            }
        }


        public function removeAllListener():void
        {
            changeListeners = new Vector.<PropertyChangeListener>();
        }

        /**
         *  Class detecting if 2 values are different.
         */
        public function getValueChangeDetector():ValueChangeDetector
        {
            if( _valueChangeDetector == null )
            {
                _valueChangeDetector = new EqualsValueChangeDetector();
            }
            return _valueChangeDetector;
        }

        /**
         *  Class detecting if 2 values are different.
         */
        public function setValueChangeDetector( value:ValueChangeDetector ):void
        {
            _valueChangeDetector = value;
        }


        public function equals( obj:Object ):Boolean
        {
            return !hasValueChanged( getValue(), obj );
        }

        /**
         *  Delegates to configured <code>ValueChangeDetector</code>.
         * @param oldValue old value to compare
         * @param newValue new value to compare
         * @return comparison result
         */
        protected function hasValueChanged( oldValue:Object, newValue:Object ):Boolean
        {
            return getValueChangeDetector().hasValueChanged( oldValue, newValue );
        }

        protected function fireValueChange( oldValue:Object, newValue:Object ):void
        {
            for each ( var listener:PropertyChangeListener in changeListeners )
            {
                listener.propertyChange( new PropertyChangeEvent( PropertyChangeEvent.PROPERTY_CHANGE, false, false, null, propertyName,
                                                                  oldValue, newValue, host ) );
            }
        }


        private function propertyChangedHandler( event:PropertyChangeEvent ):void
        {
            setValue( event.newValue );
            /*
            try
            {
                // this should be implemented in the class extending this abstract class

            }
            catch ( err:Error )
            {
                // fail silently
            }
            */
        }
    }
}